#include "SerialPort_SerialPortInputStream.h"
#include "SerialPortFactory.h"
#include "types.h"
#include "jni_utile.h"
#include <boost\shared_ptr.hpp>
#include <boost\system\error_code.hpp>
#include <boost\scoped_array.hpp>
#include <boost\asio\read.hpp>
#include <boost\asio\read_until.hpp>
#include <string>
#include <boost\bind.hpp>
#include <boost\locale.hpp>
#include <boost\regex.hpp>

extern boost::shared_ptr<SerialPortFactory> serialPortFactory;
extern JavaVM* g_jvm;
extern std::string encode;
/*
* Class:     SerialPort_SerialPortInputStream
* Method:    getchar
* Signature: (J)I
*/
JNIEXPORT jint JNICALL Java_SerialPort_SerialPortInputStream_getchar
(JNIEnv *penv, jobject obj, jlong fd) {
	if (fd < 0) {
		throw_serial_exception(penv, FD_INVALID);
		return -1;
	}
	else {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);

		if (pserialInfo == NULL) {
			throw_serial_exception(penv, PORT_NOT_OPENED);
			return -2;
		}

		Lock lock = pserialInfo->get_lock(penv, obj);
		
		if(!*lock.get()){
			throw_serial_exception(penv, "device or resource busy");
			return -1;
		}

		boost::asio::streambuf& stream = pserialInfo->get_instream();
		if (stream.size() > 0) {
			return stream.sbumpc();
		}
		else {
			boost::system::error_code ec;
			serial_port_ptr pserial_port = pserialInfo->get_serial_port_ptr();

			size_t temp = boost::asio::read_size_helper(stream, 16);
			temp = pserial_port->read_some(stream.prepare(temp), ec);
			stream.commit(temp);

			if (ec.value() != boost::system::errc::success) {
				throw_serial_exception(penv, ec.message().c_str());
				return -3;
			}

			int ch = stream.sbumpc();
			
			return ch;
		}
	}

	return -4;
}

/*
* Class:     SerialPort_SerialPortInputStream
* Method:    readSome
* Signature: (J[BII)I
*/
JNIEXPORT jint JNICALL Java_SerialPort_SerialPortInputStream_readSome
(JNIEnv *penv, jobject obj, jlong fd, jbyteArray b, jint off, jint size) {
	if (fd > 0) {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);

		if (pserialInfo == NULL) {
			throw_serial_exception(penv, PORT_NOT_OPENED);
		}else {
			if (size == 0)
				return 0;

			boost::system::error_code e;
			Lock lock = pserialInfo->get_lock(penv, obj);
			
			if(!*lock.get()){
				throw_serial_exception(penv, "device or resource busy");
				return -1;
			}

			boost::asio::streambuf& stream = pserialInfo->get_instream();
			
			size_t temp = stream.size();
			
			if(temp > 0){
				temp = size > temp ? temp : size;
			}else {
				temp = boost::asio::read_size_helper(stream, size);
				temp = pserialInfo->get_serial_port_ptr()->read_some(stream.prepare(temp), e);
				stream.commit(temp);

				if (e.value() != boost::system::errc::success) {
					throw_serial_exception(penv, e.message().c_str());
					return NULL;
				}
			}

			if (temp > 0) {
				copy_from_stream(penv, b, stream, off, temp);
				return temp;
			}
		}
	}
	else {
		throw_serial_exception(penv, FD_INVALID);
	}
	return NULL;
}

/*
* Class:     SerialPort_SerialPortInputStream
* Method:    readAsync
* Signature: (J[BILSerialPort/RecvFinish;)V
*/
JNIEXPORT void JNICALL Java_SerialPort_SerialPortInputStream_readAsync__J_3BILSerialPort_RecvFinish_2
(JNIEnv *penv, jobject obj, jlong fd, jbyteArray limit, jint time, jobject handle) {
	if (fd < 0) {
		call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::bad_file_descriptor), NULL, 0);
	}
	else {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);

		if (pserialInfo == NULL) {

			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::identifier_removed), NULL, 0);
		}

		Lock lock = pserialInfo->get_lock(penv, obj);
		
		if(!*lock.get()){
			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::device_or_resource_busy), NULL, 0);
			return;
		}

		boost::asio::streambuf& stream = pserialInfo->get_instream();

		jint size = 0;

		if (limit != NULL) {
			size = penv->GetArrayLength(limit);
		}

		if (size > 0) {
			boost::scoped_array<char> bytes(new char[size]);

			penv->GetByteArrayRegion(limit, 0, size, (jbyte*)bytes.get());
			 
			jobject ghandle = penv->NewGlobalRef(handle);

			if (NULL != ghandle) {
				boost::asio::async_read_until(*pserialInfo->get_serial_port_ptr().get(), pserialInfo->get_instream(),
					std::string(bytes.get(), size), boost::bind(warp_recv_handle_util, _1, _2, g_jvm, ghandle, pserialInfo, lock));
			}
			else {
				call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::not_enough_memory), NULL, 0);
			}
		}
		else {
			jobject ghandle = penv->NewGlobalRef(handle);

			if (NULL != ghandle) {
				boost::asio::async_read(*pserialInfo->get_serial_port_ptr().get(), pserialInfo->get_instream(),
					boost::bind(warp_recv_handle, _1, _2, g_jvm, ghandle, pserialInfo, lock));
			}
			else {
				call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::not_enough_memory), NULL, 0);
			}
		}
		
		if (time > 0) {
			//std::cout << "wait:" << time << std::endl;
			pserialInfo->async_wait_timeout(penv, handle, time);
		}
	}
}

/*
* Class:     SerialPort_SerialPortInputStream
* Method:    readAsync
* Signature: (JBILSerialPort/RecvFinish;)V
*/
JNIEXPORT void JNICALL Java_SerialPort_SerialPortInputStream_readAsync__JBILSerialPort_RecvFinish_2
(JNIEnv *penv, jobject obj, jlong fd, jbyte ch, jint time, jobject handle) {
	std::cout<<"Input->";
	if (fd < 0) {
		call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::bad_file_descriptor), NULL, 0);
	}
	else {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);

		if (pserialInfo == NULL) {

			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::identifier_removed), NULL, 0);
		}

		Lock buffLock = pserialInfo->get_lock(penv, obj);

		if(!*buffLock.get()){
			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::device_or_resource_busy), NULL, 0);
			return;
		}

		boost::asio::streambuf& stream = pserialInfo->get_instream();

		jobject ghandle = penv->NewGlobalRef(handle);
		std::cout <<"Read:" <<GetCurrentThreadId() << std::endl;
		if (NULL != ghandle) {
			boost::asio::async_read_until(*pserialInfo->get_serial_port_ptr().get(), pserialInfo->get_instream(),
				ch, boost::bind(warp_recv_handle_util, _1, _2, g_jvm, ghandle, pserialInfo, buffLock));
		}
		else {
			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::not_enough_memory), NULL, 0);
		}
		

		if (time > 0) {
			pserialInfo->async_wait_timeout(penv, handle, time);
		}
	}
}

std::size_t completion_condition(
	// Result of latest async_read_some operation.
	const boost::system::error_code& error,

	// Number of bytes transferred so far.
	std::size_t bytes_transferred,
	jint max_size
	);
/*
* Class:     SerialPort_SerialPortInputStream
* Method:    readAsync
* Signature: (JIILSerialPort/RecvFinish;)V
*/
JNIEXPORT void JNICALL Java_SerialPort_SerialPortInputStream_readAsync__JIILSerialPort_RecvFinish_2
(JNIEnv *penv, jobject obj, jlong fd, jint max_size, jint timer, jobject handle) {
	if (fd < 0) {
		call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::bad_file_descriptor), NULL, 0);
	}
	else {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);

		if (pserialInfo == NULL) {

			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::identifier_removed), NULL, 0);
		}

		Lock buffLock = pserialInfo->get_lock(penv, obj);

		if(!*buffLock.get()){
			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::device_or_resource_busy), NULL, 0);
			return;
		}

		boost::asio::streambuf& stream = pserialInfo->get_instream();

		jobject ghandle = penv->NewGlobalRef(handle);

		if (NULL == ghandle) {
			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::not_enough_memory), NULL, 0);
		}

		if (max_size > 0) {
			boost::asio::async_read(*pserialInfo->get_serial_port_ptr().get(), pserialInfo->get_instream(),
				boost::bind(completion_condition, _1, _2, max_size),
				boost::bind(warp_recv_handle_util, _1, _2, g_jvm, ghandle, pserialInfo, buffLock));
		}
		else {
			boost::asio::async_read(*pserialInfo->get_serial_port_ptr().get(), pserialInfo->get_instream(),
				boost::bind(warp_recv_handle, _1, _2, g_jvm, ghandle, pserialInfo, buffLock));
		}

		if (timer > 0) {
			pserialInfo->async_wait_timeout(penv, handle, timer);
		}
	}
}

/*
* Class:     SerialPort_SerialPortInputStream
* Method:    readAsyncRex
* Signature: (JLjava/lang/String;ILSerialPort/RecvFinish;)V
*/
JNIEXPORT void JNICALL Java_SerialPort_SerialPortInputStream_readAsyncRex
(JNIEnv *penv, jobject obj, jlong fd, jstring split, jint time, jobject handle) {
	if (fd < 0) {
		call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::bad_file_descriptor), NULL, 0);
	}
	else {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);

		if (pserialInfo == NULL) {

			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::identifier_removed), NULL, 0);
		}

		Lock buffLock = pserialInfo->get_lock(penv, obj);
		
		if(!*buffLock.get()){
			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::device_or_resource_busy), NULL, 0);
			return;
		}

		boost::asio::streambuf& stream = pserialInfo->get_instream();

		jobject ghandle = penv->NewGlobalRef(handle);

		if (NULL == ghandle) {
			call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::not_enough_memory), NULL, 0);
		}

		jsize size = 0;

		if (NULL != split) {
			size = penv->GetStringUTFLength(split);
		}

		if (size > 0) {
			const char* str = penv->GetStringUTFChars(split, NULL);
			if (NULL != str) {
				boost::regex reg(boost::locale::conv::from_utf<char>(str, encode));
				penv->ReleaseStringUTFChars(split, str);

				boost::asio::async_read_until(*pserialInfo->get_serial_port_ptr().get(), pserialInfo->get_instream(),
					reg,
					boost::bind(warp_recv_handle_util, _1, _2, g_jvm, ghandle, pserialInfo, buffLock));
			}
			else {
				call_recv_handle(penv, handle, boost::system::errc::make_error_code(boost::system::errc::not_enough_memory), NULL, 0);
			}
		}
		else {
			boost::asio::async_read(*pserialInfo->get_serial_port_ptr().get(), pserialInfo->get_instream(),
				boost::bind(warp_recv_handle, _1, _2, g_jvm, ghandle, pserialInfo, buffLock));
		}

		if (time > 0) {
			pserialInfo->async_wait_timeout(penv, handle, time);
		}
	}
}

/*
* Class:     SerialPort_SerialPortInputStream
* Method:    skip
* Signature: (JJ)J
*/
JNIEXPORT jlong JNICALL Java_SerialPort_SerialPortInputStream_skip
(JNIEnv *penv, jobject obj, jlong fd, jlong n) {
	if (fd > 0) {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);
		Lock lock = pserialInfo->get_lock(penv, obj);
		
		if(!*lock.get()){
			throw_serial_exception(penv, "device or resource busy");
			return -1;
		}

		boost::asio::streambuf& stream = pserialInfo->get_instream();
		size_t max = stream.size();
		
		n = n > max ? max : n;
		
		stream.consume(n);

		return n;
	}
	else {
		return -1;
	}
}

/*
* Class:     SerialPort_SerialPortInputStream
* Method:    available
* Signature: (J)I
*/
JNIEXPORT jint JNICALL Java_SerialPort_SerialPortInputStream_available
(JNIEnv *penv, jobject obj, jlong fd) {
	if (fd > 0) {
		SerialPortInfoPtr pserialInfo = serialPortFactory->get_serial_info(fd);
		Lock lock = pserialInfo->get_lock(penv, obj);

		if(!*lock.get()){
			throw_serial_exception(penv, "device or resource busy");
			return -1;
		}

		return pserialInfo->get_instream().size();
	}
	else {
		return 0;
	}
}

std::size_t completion_condition(
	const boost::system::error_code& error,
	std::size_t bytes_transferred,
	jint max_size) {
	if (error.value() == boost::system::errc::success) {
		int size_ret = max_size - bytes_transferred;

		return size_ret > 0 ? size_ret : 0;
	}
	else {
		return 0;
	}
}
